package com.gamejelly.proxy.client;

import com.gamejelly.proxy.pojo.RpcCall;
import com.gamejelly.proxy.pojo.RpcResponse;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;

public class ProxyHandle implements InvocationHandler {

    private final Class<?> itf;

    private final LoadBalance loadBalance;

    private final Map<String, Caller> callerMap = new ConcurrentHashMap<>();

    private final ConnectionManager connectionManager = ConnectionManager.getInstance();

    public ProxyHandle(Class<?> itf, LoadBalance loadBalance) {
        this.itf = itf;
        this.loadBalance = loadBalance;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws ExecutionException, InterruptedException, TimeoutException {

        String methodName = method.getName();
        switch (methodName) {
            case "equals":
                return proxy == args[0];
            case "hashCode":
                return System.identityHashCode(proxy);
            case "toString":
                return proxy.getClass().getName() + "@" +
                        Integer.toHexString(System.identityHashCode(proxy)) + ", with InvocationHandler " + this;
            default:
                Caller caller = callerMap.computeIfAbsent(methodName, k -> new Caller(itf, method));

                RpcCall rpcCall = new RpcCall();
                rpcCall.setId(UUID.randomUUID().toString());
                rpcCall.setClassName(caller.className);
                rpcCall.setMethodName(caller.methodName);
                rpcCall.setParameters(args);

                String addr = ProxyUtil.cancelAddress();
                if (addr == null)
                    addr = loadBalance.next();

                if (caller.isVoid()) {
                    connectionManager.oneWay(addr, rpcCall);
                    return null;
                }
                CompletableFuture<RpcResponse> future = connectionManager.call(addr, rpcCall);
                if (caller.isAsync()) {
                    return future.thenApply(RpcResponse::getData);
                }
                return future.get().getData();
        }
    }
}
